#ifndef JDEBUG_H
#define JDEBUG_H

#include <JXQCore/JXQGlobal>
#include <iostream>

namespace std {
template <typename T>
struct atomic;
}

namespace JXQ
{

class JDebugPrivate;

class JXQLIB_EXPORT JDebug
{
public:
	template <typename TChar, typename ...Args>
	inline JDebug(const std::basic_string<TChar> &format, Args&&...args);

	template <typename TChar, typename ...Args>
	inline JDebug(FILE *fp, const std::basic_string<TChar> &format, Args&&...args);

	enum Channel
	{
		StdOut,
		StdErr,
		Log,
	};
	JDebug(Channel c = StdOut);
	~JDebug();

public:
	JDebug &setChannel(Channel c);
	Channel channel() const;

public:
	static void setWordWrap(bool enable = true);
    static bool wordWrap();

private:
	J_DISABLE_COPY(JDebug)
	JDebugPrivate *d_ptr;
};

#define jDebug(...)  JXQ::JDebug(__VA_ARGS__)
#define jError()     JXQ::JDebug(JXQ::JDebug::StdErr)
#define jLog()       JXQ::JDebug(JXQ::JDebug::Log)

template <typename TChar, typename ...Args>
inline JDebug::JDebug(const std::basic_string<TChar> &format, Args&&...args) :
	JDebug()
{
    printf(format.c_str(), std::forward<Args>(args)...);
}

template <typename TChar, typename ...Args>
inline JDebug::JDebug(FILE *fp, const std::basic_string<TChar> &format, Args&&...args) :
	JDebug()
{
    fprintf(fp, format.c_str(), std::forward<Args>(args)...);
}

template <typename T>
inline JDebug &operator<<(JDebug &d, const T &t)
{
	auto c = d.channel();
    if( c == d.StdOut )
		std::cout << t << " ";
	else if( c == d.StdErr )
		std::cerr << t << " ";
	else if( c == d.Log )
		std::clog << t << " ";
	return d;
}

template <typename T>
inline JDebug &operator<<(JDebug &&d, const T &t)
{
	return d << t;
}

template <typename T>
inline JDebug &operator<<(JDebug &d, T *t)
{
	auto c = d.channel();
    if( c == d.StdOut )
		std::cout << "pointer:[ type = '" << ABI_CXA_DEMANGLE(typeid(T).name()) << "' , " << J_RCT(const void*,t) << " ] ";
	else if( c == d.StdErr )
		std::cerr << "pointer:[ type = '" << ABI_CXA_DEMANGLE(typeid(T).name()) << "' , " << J_RCT(const void*,t) << " ] ";
	else if( c == d.Log )
		std::clog << "pointer:[ type = '" << ABI_CXA_DEMANGLE(typeid(T).name()) << "' , " << J_RCT(const void*,t) << " ] ";
	return d;
}

template <typename T>
inline JDebug &operator<<(JDebug &&d, T *t)
{
	return d << t;
}

template <typename T>
inline JDebug &operator<<(JDebug &d, const std::atomic<T> &i)
{
	return d << i.operator T();
}

template <typename T>
inline JDebug &operator<<(JDebug &&d, const std::atomic<T> &i)
{
	return d << i.operator T();
}

template <typename T>
inline JDebug &operator>>(JDebug &d, T &t)
{
	std::cin >> t;
	return d;
}

template <typename T>
inline JDebug &operator>>(JDebug &&d, T &t)
{
	return d >> t;
}

template <typename T>
inline JDebug &operator>>(JDebug &d, std::atomic<T> &i)
{
	T tmp;
	std::cin >> tmp;
	i = tmp;
	return d;
}

template <typename T>
inline JDebug &operator>>(JDebug &&d, std::atomic<T> &i)
{
	return d >> i;
}

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const char c);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const char c);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const wchar_t wc);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const wchar_t wc);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const bool &b);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const bool &b);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const char *str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const char *str);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, char *str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, char *str);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const wchar_t *str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const wchar_t *str);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, wchar_t *str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, wchar_t *str);

JXQLIB_EXPORT JDebug &operator<<
(JDebug &d, std::basic_ostream<char>&(*ctrl)(std::basic_ostream<char>&));
JXQLIB_EXPORT JDebug &operator<<
(JDebug &&d, std::basic_ostream<char>&(*ctrl)(std::basic_ostream<char>&));

JXQLIB_EXPORT JDebug &operator<<
(JDebug &d, std::basic_ostream<wchar_t>&(*ctrl)(std::basic_ostream<wchar_t>&));
JXQLIB_EXPORT JDebug &operator<<
(JDebug &&d, std::basic_ostream<wchar_t>&(*ctrl)(std::basic_ostream<wchar_t>&));

#ifdef _MSC_VER // MSVC is annoying
// In an MSVC environment, putting an implementation in a source file leads to unknown bugs
// (segment errors, located in the standard library)

inline JDebug &operator<<(JDebug &d, const std::basic_string<char> *str)
{
	auto c = d.channel();
    if( c == d.StdOut )
		std::cout << '"' << str->c_str() << "\" ";
	else if( c == d.StdErr )
		std::cerr << '"' << str->c_str() << "\" ";
	else if( c == d.Log )
		std::clog << '"' << str->c_str() << "\" ";
	return d;
}

inline JDebug &operator<<(JDebug &d, const std::basic_string<wchar_t> *str)
{
	auto c = d.channel();
    if( c == d.StdOut )
		std::wcout << L"\"" << str->c_str() << L"\" ";
	else if( c == d.StdErr )
		std::wcerr << L"\"" << str->c_str() << L"\" ";
	else if( c == d.Log )
		std::wclog << L"\"" << str->c_str() << L"\" ";
	return d;
}

#else //GNU/Clang

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const std::basic_string<char> *str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const std::basic_string<wchar_t> *str);

#endif //MSVC

JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const std::basic_string<char> *str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const std::basic_string<wchar_t> *str);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const std::basic_string<char> &str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const std::basic_string<char> &str);

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const std::basic_string<wchar_t> &str);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const std::basic_string<wchar_t> &str);

template <typename Return, typename Class, typename ...Args>
JDebug &operator<<(JDebug &d, Return(Class::*method)(Args...))
{
	typedef Return(Class::*Method)(Args...);
	AddrTlr<Method> at(method);
	const void *p = J_RCT(const void*, at.addr);

	auto c = d.channel();
    if( c == d.StdOut )
		std::cout << "pointer:[ type = '" << ABI_CXA_DEMANGLE(typeid(Method).name()) << "' , " << p << " ] ";
	else if( c == d.StdErr )
		std::cerr << "pointer:[ type = '" << ABI_CXA_DEMANGLE(typeid(Method).name()) << "' , " << p << " ] ";
	else if( c == d.Log )
		std::clog << "pointer:[ type = '" << ABI_CXA_DEMANGLE(typeid(Method).name()) << "' , " << p << " ] ";
	return d;
}

template <typename Return, typename Class, typename ...Args>
inline JDebug &operator<<(JDebug &&d, Return(Class::*method)(Args...))
{
	return d << method;
}

JXQLIB_EXPORT JDebug &operator<<(JDebug &d, const unsigned char num);
JXQLIB_EXPORT JDebug &operator<<(JDebug &&d, const unsigned char num);


} //namespace JXQ


#endif //JDEBUG_H
